﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;

namespace pl.polsl.zmitac.bmmsi 
{
    public class Genetics
    {
        public enum RodzajKrzyzowania
        {
            Jednopunktowe, Dwupunktowe
        }
        public delegate int Delegat(string ciagKodowy);

        protected int liczbaPokolen, liczebnoscPokolenia;
        protected double dokladnosc;
        protected double pKrzyzowania, pMutacji; // prawodopodobieństwo
        protected RodzajKrzyzowania rodzajKrzyzowania;
        protected Delegat funkcjaPrzystosowania;
        protected string[] tabOsobnikow;
        private string[] tabRodzicow;
        private int dlugoscCiagu;
        private Random rand;
        private Statystyka stat;
        private Wyniki wyniki;

        public Genetics(int liczbaPokolen, int liczebnoscPokolenia, double pKrzyzowania, double pMutacji, RodzajKrzyzowania rKrzyzowania, Delegat funPrzystosowania, string[] tabRodzicow)
        {
            this.liczbaPokolen = liczbaPokolen;
            this.liczebnoscPokolenia = liczebnoscPokolenia;
            this.pKrzyzowania = pKrzyzowania;
            this.pMutacji = pMutacji;
            this.rodzajKrzyzowania = rKrzyzowania;
            this.funkcjaPrzystosowania = funPrzystosowania;
            this.dokladnosc = dokladnosc;
            this.tabOsobnikow = new string[liczebnoscPokolenia];
            this.tabRodzicow = new string[liczebnoscPokolenia];
            stat = new Statystyka(funkcjaPrzystosowania);
            wyniki = new Wyniki(liczebnoscPokolenia, liczbaPokolen);

            dlugoscCiagu = tabRodzicow[0].Length;

            for (int i = 0; i < liczebnoscPokolenia; i++)
            {
                if (tabRodzicow[i].Length == dlugoscCiagu)
                {
                    this.tabOsobnikow[i] = tabRodzicow[i];
                }
                else
                {
                    throw new Exception("Wszystkie genotypy powinny być równej długości");
                }
            }

            rand = new Random();
        }

        public Wyniki Oblicz()
        {            
            for (int i = 0; i < liczbaPokolen; i++)
            {
                LosujRodzicow();
                
                Krzyzowanie();
                
                Mutacja();

                wyniki.najlepszeOsobniki.Add(stat.NajlepszyOsobnik(tabOsobnikow));
                wyniki.srednie.Add(stat.SredniaOcena(tabOsobnikow));
                wyniki.odchyleniaStandardowe.Add(stat.OdchylenieStandardowe(tabOsobnikow));
            }

            wyniki.koncoweOsobniki.AddRange(tabOsobnikow);

            return wyniki;
        }

        private void LosujRodzicow()
        {
            int[] tabOcen = new int[liczebnoscPokolenia];
            int tempOcena = 0, 
                sumaOcen = 0,
                los;

            // tworzenie tablicy ocen
            for (int i=0; i<liczebnoscPokolenia; i++)
            {
                sumaOcen += tabOcen[i] = funkcjaPrzystosowania(tabOsobnikow[i]);
            }

            // losowanie następnego pokolenia z prawdopodobieństwem
            // zależnym od oceny
            for (int i = 0; i < liczebnoscPokolenia; i++)
            {
                tempOcena = 0;
                los = rand.Next(sumaOcen);
                for (int j = 0; j < liczebnoscPokolenia; j++)
                {
                    if (los >= tempOcena && los <= tempOcena + tabOcen[j])
                    {
                        tabRodzicow[i] = tabOsobnikow[j];
                        break;
                    }
                    tempOcena += tabOcen[j];
                }
            }
        }

        private void Krzyzowanie()
        {
            string osobnik1, osobnik2;
            int los;
            double dLos;

            
            for (int i = 0; i < liczebnoscPokolenia; i += 2)
            {
                // losujemy po 2 osobniki z tabeli tabRodzicow
                do
                {
                    los = rand.Next(liczebnoscPokolenia);
                    osobnik1 = tabRodzicow[los];
                }
                while (osobnik1 == string.Empty);
                tabRodzicow[los] = string.Empty;

                do
                {
                    los = rand.Next(liczebnoscPokolenia);
                    osobnik2 = tabRodzicow[los];
                }
                while (osobnik2 == string.Empty);
                tabRodzicow[los] = string.Empty;

                dLos = rand.NextDouble();
                if (dLos <= pKrzyzowania)
                {
                    if (rodzajKrzyzowania == Genetics.RodzajKrzyzowania.Jednopunktowe)
                    {
                        Krzyzuj1(ref osobnik1, ref osobnik2);
                    }
                    else
                    {
                        if (dlugoscCiagu < 3)
                        {
                            throw new Exception("Za krótki ciąg kodowy, aby przeprowadzić krzyżowanie dwupunktowe");
                        }
                        else
                        {
                            Krzyzuj2(ref osobnik1, ref osobnik2);
                        }
                    }
                }

                tabOsobnikow[i] = osobnik1;
                tabOsobnikow[i + 1] = osobnik2;
            }

            
        }

        private void Mutacja()
        {
            double mutacja = 1 / pMutacji;
            StringBuilder str = new StringBuilder("");

            for (int i = 0; i < liczebnoscPokolenia; i++)
            {
                for (int j = 0; j < dlugoscCiagu; j++)
                {
                    int los = rand.Next((int)mutacja);
                    if (los == 1)
                    {
                        if (tabOsobnikow[i][j] == '0')
                        {
                            tabOsobnikow[i] = tabOsobnikow[i].Remove(j, 1);
                            tabOsobnikow[i] = tabOsobnikow[i].Insert(j, "1");
                        }
                        else
                        {
                            tabOsobnikow[i] = tabOsobnikow[i].Remove(j, 1);
                            tabOsobnikow[i] = tabOsobnikow[i].Insert(j, "0");
                        }
                    }
                }
            }
        }

        private void Krzyzuj1(ref string osobnik1, ref string osobnik2)
        {
            int punktPodzialu = rand.Next(1, dlugoscCiagu - 1);
            string temp;

            temp = osobnik1.Substring(0, punktPodzialu);
            temp += osobnik2.Substring(punktPodzialu, osobnik2.Length - punktPodzialu);

            osobnik2 = osobnik2.Substring(0, punktPodzialu) + osobnik1.Substring(punktPodzialu, osobnik1.Length - punktPodzialu);
            osobnik1 = temp;
        }

        private void Krzyzuj2(ref string osobnik1, ref string osobnik2)
        {
            int punkt1, punkt2;
            string temp;
            StringBuilder str = new StringBuilder("");

            punkt1 = rand.Next(1, dlugoscCiagu - 2);
            punkt2 = rand.Next(punkt1 + 2, dlugoscCiagu);

            temp = osobnik1;
            str.Append(osobnik1.Substring(0, punkt1));
            str.Append(osobnik2.Substring(punkt1, punkt2 - punkt1));
            str.Append(osobnik1.Substring(punkt2, osobnik1.Length - punkt2));
            osobnik1 = str.ToString();

            str.Clear();
            str.Append(osobnik2.Substring(0, punkt1));
            str.Append(temp.Substring(punkt1, punkt2 - punkt1));
            str.Append(osobnik2.Substring(punkt2, osobnik2.Length - punkt2));
            osobnik2 = str.ToString();
        }

    }
}
